/**
 *
 */
package de.cbf.cam.dsa.person.generator;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.cbf.cam.dsa.person.entities.PersonBaseDSA;
import de.cbf.cam.dsa.person.entities.PersonSpecialsDSA;
import de.cbf.cam.dsa.person.extension.PersonExtensionPointResovler;
import de.cbf.cam.dsa.person.extension.interfaces.AbstractNamedGenerator;
import de.cbf.cam.dsa.person.extension.interfaces.IBirthDataGenerator;
import de.cbf.cam.dsa.person.extension.interfaces.INameGenerator;
import de.cbf.cam.dsa.person.extension.interfaces.IPersonSpecialsGenerator;
import de.cbf.cam.dsa.person.extension.interfaces.IRCPGenerator;
import de.cbf.cam.dsa.person.generator.entities.NameData;
import de.cbf.cam.dsa.person.generator.entities.NameDataPart;
import de.cbf.cam.dsa.person.generator.exception.NameGenerationException;
import de.cbf.cam.dsa.person.generator.exception.PersonGenerationException;
import de.cbf.cam.dsa.rcp.model.entities.CultureDSA;
import de.cbf.cam.dsa.rcp.model.entities.NameFormat;
import de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA;
import de.cbf.cam.dsa.rcp.model.entities.ProfessionLevel;
import de.cbf.cam.dsa.rcp.model.entities.RaceDSA;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.EntityNotFoundException;
import de.cbf.util.number_generator.RandomNumberGenerator;
import de.cbf.util.uuidheader.entities.UUIDReference;

/**
 * @author salter
 * TODO separate generator to own plugin that extends person ui plugin
 */
public class PersonGenerator extends AbstractNamedGenerator {
	private static final Log logger = LogFactory.getLog(PersonGenerator.class);

	protected RandomNumberGenerator		numberGenerator;

	protected IRCPGenerator				rcpGenerator;
	protected INameGenerator 			nameGenerator;
	protected IBirthDataGenerator 		birthDataGenerator;
	protected IPersonSpecialsGenerator 	specialsGenerator;
	protected ItemGenerator 			itemGenerator;

	public PersonGenerator() {
		super();
		setName("Person Generator");
		numberGenerator = RandomNumberGenerator.getInstance();
		setNameGenerator(PersonExtensionPointResovler.getInstance().getNameGenerator());
		setBirthDataGenerator(PersonExtensionPointResovler.getInstance().getBirthDataGenerator());
		setRcpGenerator(PersonExtensionPointResovler.getInstance().getRCPGenerator());
		setSpecialsGenerator(PersonExtensionPointResovler.getInstance().getSpecialsGenerator());
	}

	public PersonBaseDSA generatePerson(PersonBaseDSA person,Map<String,Object> properties) throws PersonGenerationException{
		long start = System.currentTimeMillis();
		logger.debug("generating Person from: "+person);
		logger.debug(printProperties(properties));
		if(rcpGenerator == null){
			throw new PersonGenerationException("no generator for Race/Culture/Profession found, cannot generate person");
		}
		//check and set gender
		generateGender(person,properties);

		//check if chosenRace/chosenCulture/chosenProfession are set, set accordingly
		RaceDSA 		race 		= null;
		CultureDSA 		culture 	= null;
		ProfessionDSA 	profession 	= null;
		//try to resolve entities that are already set, will generate new if they are not found
		try{
			if(person.getRace() != null){ //if race is set try to resolve it
				race 		= (RaceDSA)		rcpGenerator.resolveEntity(person.getRace(),IRCPGenerator.PROVIDER_RACE);
			}
		}catch(ClassCastException e){
			logger.error(e.getMessage());
		} catch (EntityNotFoundException e) {
			logger.error(e.getMessage());
		}
		try{
			if(person.getCulture() != null){ //if culture is set try to resolve it
				culture 	= (CultureDSA)	rcpGenerator.resolveEntity(person.getCulture(),IRCPGenerator.PROVIDER_CULTURE);
			}
		}catch(ClassCastException e){
			logger.error(e.getMessage());
		} catch (EntityNotFoundException e) {
			logger.error(e.getMessage());
		}
		try{
			if(person.getProfession() != null){ //if profession is set try to resolve it
				profession 	= (ProfessionDSA)rcpGenerator.resolveEntity(person.getProfession(),	IRCPGenerator.PROVIDER_PROFESSION);
			}
		}catch(ClassCastException e){
			logger.error(e.getMessage());
		} catch (EntityNotFoundException e) {
			logger.error(e.getMessage());
		}
		//generate missing components
		if(race == null){
			race = rcpGenerator.provideRaceBy(culture,profession,properties);
			person.setRace(UUIDReference.extractUUIDReference(race));
		}
		if(culture == null){
			culture = rcpGenerator.provideCultureBy(race,profession,properties);
			person.setCulture(UUIDReference.extractUUIDReference(culture));
		}
		if(profession == null){
			profession = rcpGenerator.provideProfessionBy(race,culture,properties);
			person.setProfession(UUIDReference.extractUUIDReference(profession));
		}

		//generate profession level if necessary
		generateProfessionLevel(person,profession,properties);

		//generate birthData
		generateBirthData(person,race,properties);

		//generate specials
		generateSpecials(person,properties,race,culture,profession);

		//generate Name
		generateName(person,culture,properties);

		//generate items
		generateItems(person,culture,profession,properties);
		//TODO generate looks

		//TODO generate social

		logger.debug("generated Person: \n"+person);
		long end = System.currentTimeMillis();
		logger.debug("generation took: "+(end-start));
		return person;
	}


	protected void generateGender(PersonBaseDSA person, Map<String, Object> properties) {
		if(person.getGender() == -1 ){
			//no gender set -> choose one
			int value = numberGenerator.getRandomNumber(2);
			if(value == 0){
				person.setGender(PersonBaseDSA.GENDER_FEMALE);
			}else{
				person.setGender(PersonBaseDSA.GENDER_MALE);
			}
		}
	}

	protected void generateProfessionLevel(PersonBaseDSA person,ProfessionDSA profession,Map<String,Object> properties) {
		//check if profession level is set and should be
		if(profession == null){
			//property not set or profession not set -> do not generate new profession level
			logger.warn("profession not set, cannot generate profession level");
			return;
		}
		Object profLevelProperty 	= properties.get(PropertyConstants.PROPERTY_PROFESSION_LEVEL);

		if (profLevelProperty instanceof String) {
			String value = (String) profLevelProperty;
			logger.debug("property \""+PropertyConstants.PROPERTY_PROFESSION_LEVEL+"\" set to: \""+value+"\"");
			ProfessionLevel result = profession.getProfessionLevels().get(value);
			if(result !=null){
				logger.debug("profession level specified was found, setting profession level to: "+result);
				//profession level is specified level of profession
				person.setProfessionLevel(result);
				return;
			}
			if("".equals(value)){
				logger.debug("generating profession level...");
				//first generate normal birthday to find age to compute profession level
				birthDataGenerator.generateBirthDayMonthYear(person, properties);
				Integer age = person.ageForYear(properties.get(PropertyConstants.PROPERTY_CURRENT_YEAR));
				logger.debug("chose age "+age+" for profession level...");
				//search levels that fit age
				List<ProfessionLevel> levels = profession.getPossibleProfessionLevelsForAge(age);
				if(levels.isEmpty()){
					logger.warn("no levels found for age: "+age);
					return;
				}
				int randomNumber = RandomNumberGenerator.getInstance().getRandomNumber(levels.size());
				ProfessionLevel chosenLevel = levels.get(randomNumber);
				logger.debug("Chose profession level: "+chosenLevel);
				person.setProfessionLevel(chosenLevel);
				return;
			}else{
				logger.warn("property: "+PropertyConstants.PROPERTY_PROFESSION_LEVEL+" set to unknown value: "+value);
			}
		}else{
			logger.debug("property: "+PropertyConstants.PROPERTY_PROFESSION_LEVEL+" not set, not generating profession level");
		}
	}

	protected void generateBirthData(PersonBaseDSA person, RaceDSA race,
			Map<String, Object> properties) {
		if(birthDataGenerator != null){
			//if profession level is specified take age constraints from there
			ProfessionLevel professionLevel = person.getProfessionLevel();
			if(professionLevel != null){
				logger.debug("profession level is set, ignoring age restriction properties");
				properties.put(PropertyConstants.PROPERTY_MIN_AGE,professionLevel.getMinAge());
				properties.put(PropertyConstants.PROPERTY_MAX_AGE,professionLevel.getMaxAge());
			}
			birthDataGenerator.generateBirthDataForPerson(person, race, properties);
		}
	}

	protected void generateName(PersonBaseDSA person, CultureDSA culture,
			Map<String, Object> properties) throws PersonGenerationException {
		if(nameGenerator != null){
			List<String> desiredAttributes = new ArrayList<String>();
			//build attributes from existing person
			//gender specific name
			if(PersonBaseDSA.GENDER_FEMALE.equals(person.getGender())){
				desiredAttributes.add(NameFormat.ATTRIBUTE_FEMALE);
			}else{
				desiredAttributes.add(NameFormat.ATTRIBUTE_MALE);
			}
			PersonSpecialsDSA specials = person.getSpecials();
			if(specials != null && specials.containsAttribute(PersonSpecialsDSA.ATTRIBUTE_ROYAL)){
				desiredAttributes.add(NameFormat.ATTRIBUTE_ROYAL);
			}

			try {
				NameData nameData = nameGenerator.generateName(culture.getCultureNameData(), desiredAttributes);
				setNameOfPerson(person,nameData);
			} catch (NameGenerationException e) {
				throw new PersonGenerationException(e);
			}
		}
	}
	/**
	 * @param person
	 * @param nameData
	 */
	protected void setNameOfPerson(PersonBaseDSA person, NameData nameData) {
		if(person.getCulture() == null || nameData == null)
			setDefaultName(person);
		else{
			person.setNameData(nameData);
		}
	}

	protected void setDefaultName(PersonBaseDSA person) {
		logger.warn("cannot create fName without chosenCulture -> using default fName");
		NameData nameData = new NameData();
		NameDataPart part;
		part = new NameDataPart();
		part.setText("John");
		nameData.addNamePart(part);

		part = new NameDataPart();
		part.setText(" ");
		nameData.addNamePart(part);

		part = new NameDataPart();
		part.setText("Doe");
		nameData.addNamePart(part);

		person.setNameData(nameData);
	}

	protected void generateSpecials(PersonBaseDSA person,
			Map<String, Object> properties, RaceDSA race, CultureDSA culture,
			ProfessionDSA profession) {

		if(specialsGenerator != null){
			specialsGenerator.generatePersonSpecials(person,properties,race,culture,profession);
		}
	}

	protected void generateItems(PersonBaseDSA person, CultureDSA culture,ProfessionDSA profession,
			Map<String, Object> properties) {
		if(itemGenerator != null){
			itemGenerator.generateItems(person,culture,profession,properties);
		}
	}

	protected String printProperties(Map<String, Object> properties) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("Generation properties:\n");
		for (Entry<String,Object> entry : properties.entrySet()) {
			buffer.append(entry.getKey()+":"+entry.getValue());
			buffer.append("\n");
		}
		return buffer.toString();
	}

	public void setRcpGenerator(IRCPGenerator ircpGenerator) {
		this.rcpGenerator = ircpGenerator;
	}

	public void setNameGenerator(INameGenerator nameGenerator) {
		this.nameGenerator = nameGenerator;
	}

	public void setBirthDataGenerator(IBirthDataGenerator birthDataGenerator) {
		this.birthDataGenerator = birthDataGenerator;
	}

	public void setSpecialsGenerator(IPersonSpecialsGenerator specialsGenerator) {
		this.specialsGenerator = specialsGenerator;
	}

	public void setItemGenerator(ItemGenerator itemGenerator) {
		this.itemGenerator = itemGenerator;
	}
}
